﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Data;
using System.Diagnostics;
using System.Data.Common;
using MicroDal.Providers;

namespace MicroDal
{
    internal class SqlParser
    {
        // HACK: We check for thoes words, and deside to STOREDPROC if not present!
        private static Regex SQL_PATTERN = new Regex(@"\b(exec|select|update|insert|delete|create|drop|alter)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private readonly Regex PARAM_PATTERN;
        private static Regex COMMENT_PATTERN = new Regex(@"(\/\*([^\*\/]+)\*\/)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);

        private Func<ParameterConfig, object, DbParameter> parameterCtor;
        private string _query;
        private string[] _names;

        public SqlParser(IDbResource resource)
        {
            parameterCtor = resource.ParameterInitializer;
            PARAM_PATTERN = new Regex(resource.ParameterPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);
        }

        public void Parse(string sql)
        {
            if (sql == null)
                throw new ArgumentNullException();

            _query = COMMENT_PATTERN.Replace(sql, string.Empty);
            _names = ParseNames(sql);
        }

        public CommandType CommandType
        {
            get
            {
                return SQL_PATTERN.IsMatch(_query) ? CommandType.Text : CommandType.StoredProcedure;
            }
        }

        public IEnumerable<DbParameter> GetParameterEnumerator(params object[] values)
        {
            // If there is only one parameter, and it initializes to null.
            if ((values == null && _names.Length > 1) || _names == null)
                yield break;

            if (values == null)
            {
                yield return parameterCtor(_names[0], DBNull.Value);
            }
            else
            {
                for (var i = 0; i < values.Length; i++)
                {
                    if (values[i] is DbParameter)
                    {
                        var par = values[i] as DbParameter;
                        if (_names.Length > i && _names[i] != par.ParameterName)
                            throw new InvalidOperationException("Declarative parameter name and provided DbParameter.ParameterName does not match.");

                        yield return par;
                    }
                    else
                    {
                        yield return parameterCtor(_names[i], values[i] ?? DBNull.Value);
                    }
                }
            }
        }

        private string[] ParseNames(string query)
        {
            if (query == null)
                throw new ArgumentNullException();

            var names = new HashSet<string>();
            int pos = 0;
            Match m;

            while (pos < query.Length)
            {
                if ((m = PARAM_PATTERN.Match(query, pos)).Success)
                {
                    if (m.Groups != null && m.Groups["name"].Success)
                    {
                        names.Add(m.Groups["name"].Value);
                    }
                    pos = m.Index + m.Length;
                }
                else
                    break;
            }
            return names.ToArray();
        }

        public string Query
        {
            get
            {
                return _query;
            }
        }
    }
}
